package com.yhq.basic.jvm.classload;

import com.yhq.basic.jvm.classload.custom.MyClassLoader1;
import com.yhq.basic.jvm.classload.custom.MyClassLoader2;
import com.yhq.basic.jvm.classload.custom.MyClassLoader3;
import com.yhq.basic.jvm.classload.po.LinkTestInstance;
import com.yhq.basic.jvm.classload.po.SalaryCal;
import com.yhq.basic.jvm.classload.service.SalaryCalService;
import com.yhq.basic.jvm.classload.service.UserService;
import lombok.SneakyThrows;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.ServiceLoader;

/**
 * 一、类的生命周期，最重要的还是加载
 * 1. 加载
 * 2.链接
 * 2.1 验证、
 * 2.2 准备(为静态变量设置初始值，为静态常量赋指定值)、
 * 2.3 解析(符号引用改为直接引用)
 * 3.初始化(为静态变量赋值)
 * 3.使用
 * 4.卸载
 * 二、类加载三要素
 * 1.每个类加载对已加载的类都有一个缓存
 * 2.向上查找，向下加载
 * 3.沙箱机制，体现在哪些方面：
 * 双亲委派机制，以及增加了一层保险：findClass方法->defineClass方法->preDefineClass方法其他类加载器加载java.包中的类
 * <p>
 * 三、必备掌握技能
 * 1.引入外部包
 * 2.自定义类加载器
 * 3.自定义类加载器，打破双亲委派机制
 * 4.不重启实现热加载
 * 5.java SPI实现 ：https://rgb-24bit.github.io/blog/2019/java-spi.html
 * 四、分析设计原理
 * 类加载的双亲委派机制使用了责任链设计模式
 * 实现了模板方法设计模式： classLoader提供了findClass 和loadClass 方法，子类实现findClass可加载自定义类路径，子类实现loadClass可打破双亲委派机制等等
 *
 * @author hqyin
 * @date 2024/7/30 4:03 PM
 */
public class TestMajor {
    public static void main(String[] args) {
        //打印类加载体系
//        testShowClassLoaderSystem();
        //验证链接过程
//        testLink();
        //引入外部包加薪，testSalary_importJar方法测试要注释掉本项目的SalaryCal.java，不然会加载当前项目的SalaryCal
//        testSalary();
//        testSalary_importJar();
        // 自定义类加载器，测试要注释掉本项目的SalaryCal.java，不然会加载当前项目的SalaryCal
//        testSalary_MyClassLoader1();
        // 自定义类加载器，打破双亲委派机制
//        testSalary_MyClassLoader2();
        // 不重启实现热加载
//        testSalary_HotLoading();
        //java SPI实现 默认是当前线程的类加载
//        testSalary_ServiceLoader();
        //java SPI实现 改成自定义加载器
        test_spi();

    }

    private static void test_spi() {
        ServiceLoader<UserService> loader = ServiceLoader.load(UserService.class);
        Iterator<UserService> iterator = loader.iterator();
        while (iterator.hasNext()) {
            UserService userService = iterator.next();
            userService.display();
        }
    }


    private static void testSalary_ServiceLoader() {
        ServiceLoader<SalaryCalService> loader = ServiceLoader.load(SalaryCalService.class);
        Iterator<SalaryCalService> iterator = loader.iterator();
        Double salary = 15000.00;
        while (iterator.hasNext()) {
            SalaryCalService salaryCalService =  iterator.next();
            System.out.println("=========>classLoader----"+salaryCalService.getClass().getClassLoader());
            Double money = salaryCalService.cal(salary);
            System.out.println("实际到⼿Money:" + money);
        }

    }

    @SneakyThrows
    private static void testSalary_HotLoading() {
        Double salary = 15000.00;
        Double money;
        URL jarPath = new URL("file:/Users/hqyin/Documents/MyProjects/java-basic/out/artifacts/SalaryCal/SalaryCal.jar");
        //模拟不停机状态
        while (true) {
            try {
                MyClassLoader2 myClassLoader2 = new MyClassLoader2(new URL[]{jarPath});
                money = calSalary(salary, myClassLoader2);
                System.out.println("实际到⼿Money:" + money);
            } catch (Exception e) {
                System.out.println("加载出现异常 ：" + e);
            }
            Thread.sleep(5000);
        }
    }

    @SneakyThrows
    private static void testSalary_MyClassLoader2() {
        Double salary = 15000.00;
        Double money;
        URL jarPath = new URL("file:/Users/hqyin/Documents/MyProjects/java-basic/out/artifacts/SalaryCal/SalaryCal.jar");
        MyClassLoader2 myClassLoader2 = new MyClassLoader2(new URL[]{jarPath});
        //模拟不停机状态
        while (true) {
            try {
                money = calSalary(salary, myClassLoader2);
                System.out.println("实际到⼿Money:" + money);
            } catch (Exception e) {
                System.out.println("加载出现异常 ：" + e);
            }
            Thread.sleep(5000);
        }
    }

    /**
     * idea 快速对某个类打包 方法：https://blog.51cto.com/u_14318784/2812542
     * 注意，如果class文件有包名，则需要创建和包名一样的文件夹，不然找不到文件
     */
    @SneakyThrows
    private static void testSalary_importJar() {
        Double salary = 15000.00;
        Double money = 0.00;
        URL jarPath = new URL("file:/Users/hqyin/Documents/MyProjects/java-basic/out/artifacts/SalaryCal/SalaryCal.jar");
        URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{jarPath});
        //模拟不停机状态
        while (true) {
            try {
                money = calSalary(salary, urlClassLoader);
                System.out.println("实际到⼿Money:" + money);
            } catch (Exception e) {
                System.out.println("加载出现异常 ：" + e);
            }
            Thread.sleep(5000);
        }
    }

    @SneakyThrows
    private static void testSalary_MyClassLoader1() {
        Double salary = 15000.00;
        Double money = 0.00;
        URL jarPath = new URL("file:/Users/hqyin/Documents/MyProjects/java-basic/out/artifacts/SalaryCal/SalaryCal.jar");
        MyClassLoader1 myClassLoader1 = new MyClassLoader1(new URL[]{jarPath});
        //模拟不停机状态
        while (true) {
            try {
                money = calSalary(salary, myClassLoader1);
                System.out.println("实际到⼿Money:" + money);
            } catch (Exception e) {
                System.out.println("加载出现异常 ：" + e);
            }
            Thread.sleep(5000);
        }
    }

    @SneakyThrows
    private static void testSalary() {
        Double salary = 15000.00;
        Double money = 0.00;
        //模拟不停机状态
        while (true) {
            try {
                SalaryCal salaryCal = new SalaryCal();
                money = salaryCal.cal(salary);
                System.out.println("实际到⼿Money:" + money);
            } catch (Exception e) {
                System.out.println("加载出现异常 ：" + e.getMessage());
            }
            Thread.sleep(5000);
        }

    }

    /**
     * 打印类加载体系以及加载的目录
     * 观察父子关系： AppClassLoader->ExtClassLoader->BootStrap ClassLoader
     */
    @SneakyThrows
    private static void testShowClassLoaderSystem() {
        //父子关系 AppClassLoader->ExtClassLoader->BootStrap ClassLoader
        ClassLoader cl = TestMajor.class.getClassLoader();
        System.out.println("cl >" + cl);
        System.out.println("parent of cl >" + cl.getParent());
        // BootStrap Classloader由C++开发，是JVM虚拟机的⼀部分，本身不是JAVA类。因此打印是null
        System.out.println("grand parent of cl >" + cl.getParent().getParent());

        // String,Int等基础类由BootStrap Classloader加载。
        ClassLoader cl2 = String.class.getClassLoader();
        System.out.println("cl2 > " + cl2);
        System.out.println("classLoader of java.util.List >" + cl.loadClass("java.util.List").getClass().getClassLoader());

        // java指令可以通过增加-verbose:class参数在启动时打印出类加载情况
        // 这些参数来⾃于 sun.misc.Launcher 源码
        // BootStrap Classloader，加载java基础类
        System.out.println("BootStrap ClassLoader加载⽬录：" +
                System.getProperty("sun.boot.class.path"));
        // Extention Classloader 加载⼀些扩展类。 可通过-D java.ext.dirs另⾏指定⽬录
        System.out.println("Extention ClassLoader加载⽬录：" +
                System.getProperty("java.ext.dirs"));
        // AppClassLoader 加载CLASSPATH，应⽤下的Jar包。可通过-D java.class.path另⾏指定⽬录
        System.out.println("AppClassLoader加载⽬录：" +
                System.getProperty("java.class.path"));


    }

    /**
     * 验证链接过程
     * 分析：结果-10，原因：LinkTestInstance.instance 访问静态变量会出发初始化，即加载>链接>初始化
     * 但由于LinkTestInstance.instance先调用构造方法时，price还处于链接的准备阶段，默认值0。因此结果是0-10=-10
     * 解决这个问题：方法一：price 静态变量加final变成静态常量（准备阶段为静态常量赋指定值）；方法二：price静态变量位置调整到instance静态变量前面；
     */
    private static void testLink() {
        System.out.println(LinkTestInstance.instance.finalPrice);
    }

//    private static Double calSalary(Double salary) {
//        SalaryCal caler = new SalaryCal();
//        return caler.cal(salary);
//    }

    @SneakyThrows
    private static Double calSalary(Double salary, ClassLoader classLoader) {

        Class<?> clazz = classLoader.loadClass("com.yhq.basic.jvm.classload.po.SalaryCal");
        System.out.println("=========>classLoader----"+clazz.getClassLoader());
        if(null != clazz) {
            Object object = clazz.newInstance();
            return (Double)clazz.getMethod("cal", Double.class).invoke(object, salary);
        }
        return -1.0;
    }
}
